#include <pthread.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include "blocking_queue.h"
/*
 * blocking_queue.c
 */

void queue_init(queue_t* queue, int max_size, int timeout) {
	queue->head = queue->tail = NULL;
	queue->size = 0;
	queue->max_size = max_size;
	queue->timeout = timeout;
	if (pthread_mutex_init(&(queue->mutex), NULL)) {
		fprintf(stderr, "blocking_queue:Could not create a mutex\n");
		exit(-1);
	}
	if (pthread_cond_init(&(queue->consumed_cv), NULL)) {
		fprintf(stderr, "blocking_queue:Could not create a queue consumed cond var\n");
		exit(-1);
	}
	if (pthread_cond_init(&(queue->produced_cv), NULL)) {
		fprintf(stderr, "blocking_queue:Could not create a queue produced cond var\n");
		exit(-1);
	}
}


int queue_put(queue_t* queue, data_t data) {
	struct timespec to;
	pthread_mutex_lock(&(queue->mutex));
	while (queue->size >= queue->max_size) {
		to.tv_sec = time(NULL) + queue->timeout;
		to.tv_nsec = 0;
		int err = pthread_cond_timedwait(&(queue->consumed_cv), &(queue->mutex), &to);

		if (err == ETIMEDOUT){
			pthread_mutex_unlock(&(queue->mutex));
			return ETIMEDOUT;
		}
	}

	node_t* node = malloc(sizeof(node_t));
	node->data = data;
	//adding new node to the tail of the queue
	node->next = NULL;
	node_t* prev = queue->tail;
	//queue isn't empty
	if (prev != NULL) {
		prev->next = node;
	}
	queue->tail = node;
	//queue is empty
	if (queue->head == NULL) {
		queue->head = node;
	}
	queue->size++;

	pthread_mutex_unlock(&(queue->mutex));
	pthread_cond_broadcast(&(queue->produced_cv));
	return 0;
}

data_t queue_get(queue_t* queue) {
	struct timespec to;
	pthread_mutex_lock(&(queue->mutex));
	while (queue->size <= 0) {
		to.tv_sec = time(NULL) + queue->timeout;
		to.tv_nsec = 0;
		int err = pthread_cond_timedwait(&(queue->produced_cv), &(queue->mutex), &to);
		if (err == ETIMEDOUT){
			pthread_mutex_unlock(&(queue->mutex));
			return NULL;
		}
	}

	//can't happen, since this is blocking queue
	//	if(queue->head == NULL){
	//		return NULL;
	//	}
	node_t* node = queue->head;
	//queue of size 1
	if (queue->head == queue->tail) {
		queue->head = queue->tail = NULL;
	} else {
		queue->head = node->next;
	}
	//clear pointers to internal structure
	node->next = NULL;
	data_t data = node->data;
	free(node);
	queue->size--;

	pthread_mutex_unlock(&(queue->mutex));
	pthread_cond_broadcast(&(queue->consumed_cv));
	return data;
}
void queue_destroy(queue_t* queue) {
	data_t data;
	while ((data = queue_get(queue)) != NULL) {
		free(data);
	}

	pthread_cond_destroy(&(queue->produced_cv));
	pthread_cond_destroy(&(queue->consumed_cv));
	pthread_mutex_destroy(&(queue->mutex));
}

